package com.bamboo.tool.util;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.StrUtil;
import com.bamboo.tool.config.BambooToolComponent;
import com.bamboo.tool.config.model.PsiClassCache;
import com.bamboo.tool.constant.StringConstant;
import com.bamboo.tool.db.entity.BambooApiMethod;
import com.bamboo.tool.entity.*;
import com.bamboo.tool.entity.treenode.ProjectNodeInfo;
import com.bamboo.tool.enums.*;
import com.bamboo.tool.factory.FrameworkExecute;
import com.bamboo.tool.view.component.tree.*;
import com.intellij.ide.util.PsiNavigationSupport;
import com.intellij.openapi.fileEditor.impl.NonProjectFileWritingAccessProvider;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.pom.Navigatable;
import com.intellij.psi.*;
import com.intellij.psi.impl.compiled.ClsClassImpl;
import com.intellij.psi.impl.java.stubs.index.JavaAnnotationIndex;
import com.intellij.psi.impl.source.PsiClassReferenceType;
import com.intellij.psi.impl.source.PsiJavaFileImpl;
import com.intellij.psi.search.ProjectScope;
import com.intellij.psi.util.PsiUtil;
import net.datafaker.Faker;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.tree.TreePath;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * @author bamboo
 */
public class PsiUtils {
    public static Faker faker = new Faker(new Locale("en-US"));
    public static Faker enFaker = new Faker(new Locale("en-US"));

    public static List<PsiClassCache> getALLPsiClass(Project project, List<AnnotationInfoSetting> annotationInfoSettings) {
        List<AnnotationInfoSetting> infos = annotationInfoSettings.stream().filter(e -> AnnotationScope.CLASS.getCode().equals(e.getScope())).filter(e -> e.getEffect().contains("scann")).collect(Collectors.toList());
        List<PsiClassCache> caches = new ArrayList<>();
        infos.forEach(e -> {
            Collection<PsiAnnotation> psiAnnotations = JavaAnnotationIndex.getInstance().get(e.getAnnotationName(), project, ProjectScope.getContentScope(project));
            caches.add(new PsiClassCache(e, psiAnnotations));
        });

        return caches;
    }

    public static RootNode convertToRoot(List<BambooApiMethod> apiMethods) {

        Map<Integer, ProjectNodeInfo> projectInfoMap = BambooToolComponent.getStoreService().getAllProjectInfos().stream().map(e -> {
            ProjectNodeInfo projectNodeInfo = new ProjectNodeInfo();
            projectNodeInfo.setProjectId(e.getId());
            ProjectOtherInfo projectOtherInfo = e.getProjectOtherInfo();
            Map<String, String> desc = projectOtherInfo.getDesc();
            if (StrUtil.isEmpty(desc.get(StringConstant.RENAME))) {
                projectNodeInfo.setName(e.getProjectName());
            } else {
                projectNodeInfo.setName(desc.get(StringConstant.RENAME));
            }
            return projectNodeInfo;
        }).collect(Collectors.toMap(e -> e.getProjectId(), e -> e));
        Map<Integer, ProjectNode> projectNodeMap = new ConcurrentHashMap<>();
        Map<String, ModuleNode> moduleNodeMap = new ConcurrentHashMap<>();
        Map<String, ClassNode> classNodeMap = new ConcurrentHashMap<>();
        AtomicInteger apiCount = new AtomicInteger();
        apiMethods.forEach(e -> {
            apiCount.getAndIncrement();
            String modelName = e.getModelName();
            String className = e.getClassName();
            Integer projectId = e.getProjectId();

            ModuleNode moduleNode = new ModuleNode(modelName, projectId);
            ModuleNode moduleNode1 = moduleNodeMap.putIfAbsent(modelName, moduleNode);
            if (moduleNode1 != null) {
                moduleNode = moduleNode1;
            }
            NoteData noteData = new NoteData();
            noteData.setPersistenceInfo(e.getClassPersistenceInfo());
            if (StringUtil.isNotEmpty(e.getClassDesc())) {
                noteData.setName(e.getClassDesc());
            } else {
                noteData.setName(className);
            }
            noteData.setClassId(e.getClassId());
            ClassNode classNode = new ClassNode(noteData);
            ClassNode classNode1 = classNodeMap.putIfAbsent(modelName + className, classNode);
            if (classNode1 != null) {
                classNode = classNode1;
            }
            moduleNode.add(classNode);

            classNode.getSource().setMethodCount(classNode.getSource().getMethodCount() + 1);
            classNode.add(new MethodNode(e));
            ProjectNode projectNode = new ProjectNode(projectInfoMap.get(projectId));
            ProjectNode projectNode1 = projectNodeMap.putIfAbsent(projectId, projectNode);
            if (projectNode1 != null) {
                projectNode = projectNode1;
            }
            projectNode.add(moduleNode);

        });
        RootNode root = new RootNode("apis(" + apiCount.get() + ")");
        projectNodeMap.values().forEach(e -> root.add(e));
        return root;
    }

    public static void openFile(@NotNull VirtualFile file, @NotNull Project project, String methodName, String methodParams) {
        if (file == null) {
            return;
        }

        if (project == null) {
            return;
        }

        NonProjectFileWritingAccessProvider.allowWriting(Collections.singletonList(file));
        PsiFile psiFile = PsiUtil.getPsiFile(project, file);
        if (psiFile instanceof PsiJavaFile) {
            PsiClass childClass = ((PsiJavaFileImpl) psiFile).findChildByClass(PsiClass.class);
            if (childClass != null) {
                Map<String, List<PsiMethod>> methodMap = Arrays.stream(childClass.getAllMethods()).collect(Collectors.groupingBy(PsiMethod::getName, Collectors.toList()));
                List<PsiMethod> psiMethods = methodMap.get(methodName);
                Navigatable navigatable = null;
                if (CollUtil.isNotEmpty(psiMethods)) {
                    if (psiMethods.size() == 1) {
                        PsiMethod psiMethod = psiMethods.get(0);
                        PsiElement navigationElement = psiMethod.getNavigationElement();
                        navigatable = (Navigatable) navigationElement;

                    } else {
                        PsiElement psiElement = psiMethods.stream().filter(e -> e.getName().equals(methodName)).filter(e -> {
                            if (methodParams != null) {
                                PsiParameterList parameters = e.getParameterList();
                                List<String> methodParameters = Arrays.stream(parameters.getParameters()).map(a -> a.getType().getCanonicalText()).collect(Collectors.toList());
                                return methodParams.equals(methodParameters.toString());
                            }
                            return false;
                        }).map(PsiElement::getNavigationElement).findFirst().get();
                        navigatable = (Navigatable) psiElement;
                    }
                } else {

                }

                navigatable.navigate(true);
            } else {
                PsiNavigationSupport.getInstance().createNavigatable(project, file, -1).navigate(true);
            }
        } else {
            PsiNavigationSupport.getInstance().createNavigatable(project, file, -1).navigate(true);
        }
    }

    public static JavaFieldInfo buildJavaDataType(PsiType psiType, String name, Map<String, PsiType> substitutorTypeMap, Integer recursiveDepth, Map<String, Map<String, AnnotationInfoSetting>> attributeMap, JavaClassValidate javaClassValidate) {
        recursiveDepth++;
        JavaFieldInfo fieldInfo = new JavaFieldInfo();
        if (StringUtil.isNotEmpty(name)) {
            fieldInfo.setFieldName(name);
        }
        fieldInfo.setFieldType(DataType.OBJECT);
        if (psiType == null) {
            return fieldInfo;
        }
        String canonicalText = psiType.getCanonicalText();

        //java基础类型处理
        DataType dataType = javaClassValidate.getSpecifyType(canonicalText);
        if (!Objects.isNull(dataType) && dataType.isReturn()) {
            fieldInfo.setFieldType(dataType);
            fieldInfo.setDefaultValue(dataType.getDefaultValue());
            return fieldInfo;
        }


        if (psiType instanceof PsiClassType) {
            PsiClassType psiClassType = (PsiClassType) psiType;
            String className = psiClassType.getClassName();
            DataType classDataType = javaClassValidate.getSpecifyType(className);

            if (classDataType != null && (classDataType.equals(DataType.LIST) || classDataType.equals(DataType.SET))) {
                fieldInfo.setArrayType(classDataType);

                PsiType[] parameters = psiClassType.getParameters();
                if (parameters.length > 0 && recursiveDepth < 10) {

                    for (PsiType parameter : parameters) {
                        JavaFieldInfo array = buildJavaDataType(parameter, "array", substitutorTypeMap, recursiveDepth, attributeMap, javaClassValidate);
                        if (CollUtil.isEmpty(array.getChildren())) {
                            fieldInfo.setEnums(array.getEnums());
                            fieldInfo.setFieldType(array.getFieldType());
                        } else {
                            fieldInfo.setFieldType(DataType.OBJECT);
                            fieldInfo.setChildren(array.getChildren());
                        }

                    }
                }
                return fieldInfo;
            }

            boolean generics = javaClassValidate.isGenerics(canonicalText);
            //如果不包含.且为Class的，则是泛型
            if (generics) {
                PsiType genericsPsiType = substitutorTypeMap.get(canonicalText);
                if (genericsPsiType != null) {
                    if (recursiveDepth < 10) {

                        JavaFieldInfo javaFieldInfo = buildJavaDataType(genericsPsiType, name, substitutorTypeMap, recursiveDepth, attributeMap, javaClassValidate);
                        List<JavaFieldInfo> children = javaFieldInfo.getChildren();
                        if (CollUtil.isNotEmpty(children)) {
                            fieldInfo.getChildren().addAll(children);
                        } else {
                            fieldInfo.setFieldType(javaFieldInfo.getFieldType());
                        }
                    }
                }
                return fieldInfo;
            }

            if (!Objects.isNull(classDataType) && classDataType.isReturn()) {
                fieldInfo.setFieldType(classDataType);
                fieldInfo.setDefaultValue(classDataType.getDefaultValue());
                return fieldInfo;
            }
            if (psiType instanceof PsiClassReferenceType) {
                PsiClassReferenceType psiClassReferenceType = (PsiClassReferenceType) psiType;

                PsiClass psiClass = psiClassReferenceType.resolve();
                if (psiClass instanceof ClsClassImpl) {
                    PsiClass sourceMirrorClass = ((ClsClassImpl) psiClass).getSourceMirrorClass();
                    if (sourceMirrorClass != null) {
                        psiClass = ((ClsClassImpl) psiClass).getSourceMirrorClass();
                    }
                }

                if (psiClass == null) {
                    return fieldInfo;
                }

                if (psiClass.isEnum()) {
                    fieldInfo.setFieldType(DataType.STRING);
                    List<String> enums = Arrays.stream(psiClass.getAllFields()).filter(e -> e instanceof PsiEnumConstant).map(e -> e.getName()).collect(Collectors.toList());
                    fieldInfo.setEnums(enums);
                    return fieldInfo;
                }

                boolean ignoredClass = javaClassValidate.isFieldIgnoredClass(psiClass.getQualifiedName());
                if (ignoredClass) {
                    Collection<PsiType> values = psiClassReferenceType.resolveGenerics().getSubstitutor().getSubstitutionMap().values();
                    if (values.size() > 0) {
                        List<PsiType> list = new ArrayList<>(values);
                        if (list.get(0) != null) {
                            JavaFieldInfo javaFieldInfo = buildJavaDataType(list.get(0), name, substitutorTypeMap, recursiveDepth, attributeMap, javaClassValidate);
                            List<JavaFieldInfo> children = javaFieldInfo.getChildren();
                            if (CollUtil.isNotEmpty(children)) {
                                fieldInfo.getChildren().addAll(children);
                            } else {
                                fieldInfo.setFieldType(javaFieldInfo.getFieldType());
                            }
                        }
                        return fieldInfo;
                    }

                    return fieldInfo;
                }


                List<PsiClass> psiClassList = new ArrayList<>();
                getAllSuperClass(psiClassList, psiClass, javaClassValidate, 0);
                if (psiClassList.size() > 0) {
                    substitutorTypeMap = getStringPsiTypeMap(psiClassReferenceType, psiClass);
                }
                for (int i = 0; i < psiClassList.size(); i++) {
                    PsiClass psiClass1 = psiClassList.get(i);
                    PsiField[] fields = psiClass1.getFields();
                    if (fields.length > 0 && recursiveDepth < 10) {
                        for (PsiField field : fields) {

                            PsiType fieldType = field.getType();
                            if (hasStaticModifier(field.getModifierList()) || hasFinalModifier(field.getModifierList())) {
                                // 如果字段是static或final则跳过
                                continue;
                            }

                            JavaFieldInfo javaFieldInfo = buildJavaDataType(fieldType, field.getName(), substitutorTypeMap, recursiveDepth, attributeMap, javaClassValidate);
                            Map<String, String> desc = javaFieldInfo.getDesc();
                            JavaDocComment javaDocComment = null;
                            if (field.getDocComment() == null && field.canNavigateToSource() && field.getNavigationElement() instanceof PsiField) {
                                PsiField navigationElement = (PsiField) field.getNavigationElement();
                                javaDocComment = JavaDocComment.buildJavaDocComment(navigationElement.getDocComment());
                            } else {
                                javaDocComment = JavaDocComment.buildJavaDocComment(field.getDocComment());
                            }
                            if (javaDocComment != null) {
                                String deprecatedDocTag = javaDocComment.getOneJavaDocTag(FieldDocTags.DEPRECATED.getCode());
                                String oneJavaDocTag = javaDocComment.getOneJavaDocTag(FieldDocTags.IGNORE.getCode());
                                String apiNoteTag = javaDocComment.getOneJavaDocTag(FieldDocTags.API_NOTE.getCode());
                                String mockTag = javaDocComment.getOneJavaDocTag(FieldDocTags.MOCK.getCode());
                                //是否弃用
                                if (deprecatedDocTag != null) {
                                    continue;
                                }
                                // 是否忽略
                                if (oneJavaDocTag != null) {
                                    javaFieldInfo.setIsIgnore(Boolean.TRUE);
                                }
                                if (CharSequenceUtil.isNotEmpty(apiNoteTag)) {
                                    desc.put(DescType.API_NOTE.getCode(), apiNoteTag);
                                }
                                if (CharSequenceUtil.isNotEmpty(javaDocComment.getDescription())) {
                                    desc.put(DescType.JAVA_DOC.getCode(), javaDocComment.getDescription());
                                }
                                if (CharSequenceUtil.isNotEmpty(mockTag)) {
                                    javaFieldInfo.setExampleValue(mockTag);
                                }
                            }
                            PsiAnnotation[] annotations = field.getAnnotations();
                            if (annotations.length > 0) {
                                Map<String, AnnotationInfo> annotationInfoMap = FrameworkExecute.builAnnotationInfo(annotations);
                                for (AnnotationInfo annotationInfo : annotationInfoMap.values()) {

                                    AnnotationInfoSetting setting = FrameworkExecute.getAnnotationInfoByCode(attributeMap, AnnotationScope.Field.getCode(), annotationInfo.getAnnotationName());
                                    // 如果未配置该注解，则跳过不获取注解属性
                                    if (Objects.isNull(setting)) {
                                        continue;
                                    }
                                    Map<String, List<String>> annotationAttributes = annotationInfo.getAnnotationAttributs();
                                    Map<String, List<String>> otherParams = setting.getOtherParams();

                                    List<AnnotationParam> params = setting.getParams();

                                    params.stream().forEach(param -> {
                                        List<String> strings = annotationAttributes.get(param.getName());
                                        if (CollUtil.isEmpty(strings)) {
                                            strings = param.getDefaultValues();
                                        }
                                        if (CollUtil.isNotEmpty(strings)) {
                                            OtherParameterUtil.fieldAnnotation(param.getType(), strings, javaFieldInfo, setting.getFramework().getName(), param);
                                        }

                                    });
                                    otherParams.forEach((key, value) -> {
                                        if (CollUtil.isNotEmpty(value)) {
                                            OtherParameterUtil.fieldAnnotation(key, value, javaFieldInfo, setting.getFramework().getName(), null);
                                        }
                                    });

                                }
                            }
                            if (!javaFieldInfo.getIsIgnore()) {
                                fieldInfo.getChildren().add(javaFieldInfo);
                            }

                        }
                    }
                }
            }

        } else if (psiType instanceof PsiArrayType) {
            JavaFieldInfo javaFieldInfo = buildJavaDataType(psiType.getDeepComponentType(), name, substitutorTypeMap, recursiveDepth, attributeMap, javaClassValidate);
            List<JavaFieldInfo> children = javaFieldInfo.getChildren();
            fieldInfo.setArrayType(DataType.ARRAY);
            if (CollUtil.isNotEmpty(children)) {
                fieldInfo.setFieldType(DataType.OBJECT);
                fieldInfo.getChildren().addAll(children);
            } else {
                fieldInfo.setFieldType(javaFieldInfo.getFieldType());
            }
        } else {
            System.out.printf("");
        }


        return fieldInfo;


    }

    private static Map<String, PsiType> getStringPsiTypeMap(PsiClassReferenceType fieldType, PsiClass psiClass) {
        PsiSubstitutor substitutor = fieldType.resolveGenerics().getSubstitutor();
        Map<String, PsiType> substitutorTypeMap = new HashMap<>(4);
        substitutor.getSubstitutionMap().forEach((k, v) -> {
            String genericName = k.getName();
            if (genericName != null && v != null) {
                substitutorTypeMap.put(genericName, v.getDeepComponentType());
            }
        });
        psiClass.getExtendsListTypes();
        PsiClassType[] superTypes = psiClass.getSuperTypes();
        if (superTypes.length > 0) {
            for (int i = 0; i < superTypes.length; i++) {
                PsiSubstitutor supersubstitutor = superTypes[i].resolveGenerics().getSubstitutor();
                supersubstitutor.getSubstitutionMap().forEach((k, v) -> {
                    String genericName = k.getName();
                    if (genericName != null && v != null && substitutorTypeMap.get(genericName) == null) {
                        substitutorTypeMap.put(genericName, v.getDeepComponentType());
                    }
                });
            }
        }

        return substitutorTypeMap;
    }

    /**
     * 是否是静态(static)方法
     *
     * @param target PsiModifierList的实现类
     * @return bool
     * @see PsiUtils#hasModifier(PsiModifierList, String)
     */
    public static boolean hasStaticModifier(@Nullable PsiModifierList target) {
        return PsiUtils.hasModifier(target, PsiModifier.STATIC);
    }

    /**
     * 是否是最终(final)方法
     *
     * @param target PsiModifierList的实现类
     * @return bool
     * @see PsiUtils#hasModifier(PsiModifierList, String)
     */
    public static boolean hasFinalModifier(@Nullable PsiModifierList target) {
        return PsiUtils.hasModifier(target, PsiModifier.FINAL);
    }

    /**
     * 是否具有指定修饰符
     *
     * @param target PsiModifierList的实现类
     * @return bool
     */
    public static boolean hasModifier(@Nullable PsiModifierList target, @PsiModifier.ModifierConstant @NotNull String modifier) {
        if (target == null) {
            return false;
        }
        // 是否具有修饰符属性
        return target.hasModifierProperty(modifier);
    }

    public static void getAllSuperClass(List<PsiClass> classList, PsiClass psiClass, JavaClassValidate javaClassValidate, Integer count) {
        if (count > 5) {
            return;
        }
        String name = psiClass.getName();
        String qualifiedName = psiClass.getQualifiedName();
        DataType specifyType = javaClassValidate.getSpecifyType(qualifiedName);
        if (specifyType != null && (specifyType.equals(DataType.MAP) || specifyType.equals(DataType.LIST) || specifyType.equals(DataType.SET) || specifyType.equals(DataType.FILE))) {
            return;
        }
        classList.add(psiClass);
        PsiClassType[] extendsListTypes = psiClass.getExtendsListTypes();
        if (extendsListTypes.length > 0) {
            count++;
            for (int i = 0; i < extendsListTypes.length; i++) {
                PsiClassType extendsListType = extendsListTypes[i];
                PsiClass resolve = extendsListType.resolve();
                if (resolve != null) {
                    getAllSuperClass(classList, resolve, javaClassValidate, count);
                }

            }
        }
    }

    public static QueryClassParameter getQueryClassParameter(ApiTree apiTree) {
        TreePath[] selectionPaths = apiTree.getSelectionPaths();
        QueryClassParameter queryClassParam = new QueryClassParameter();
        for (TreePath selectionPath : selectionPaths) {
            Object lastPathComponent = selectionPath.getLastPathComponent();

            if (lastPathComponent instanceof ClassNode) {
                ClassNode classNode = (ClassNode) lastPathComponent;
                NoteData source = classNode.getSource();
                queryClassParam.getClassIds().add(source.getClassId());
            }
            if (lastPathComponent instanceof ModuleNode) {
                ModuleNode moduleNode = (ModuleNode) lastPathComponent;
                Integer projectId = moduleNode.getProjectId();
                Map<Integer, List<String>> projects = queryClassParam.getProjects();
                List<String> models = projects.get(projectId);
                if (CollectionUtil.isEmpty(models)) {
                    models = new ArrayList<>();
                    projects.put(projectId, models);
                }
                models.add(moduleNode.getSource());
            }
            if (lastPathComponent instanceof ProjectNode) {
                ProjectNode projectNode = (ProjectNode) lastPathComponent;
                queryClassParam.getProjects().put(projectNode.getSource().getProjectId(), new ArrayList<>());

            }
            if (lastPathComponent instanceof MethodNode) {
                MethodNode methodNode = (MethodNode) lastPathComponent;
                queryClassParam.getMethodIds().add(methodNode.getSource().getMethodId());
            }
            if (lastPathComponent instanceof RootNode) {
                List<Integer> projectIds = BambooToolComponent.getStoreService().getAllProjectInfos().stream().map(e -> e.getId()).collect(Collectors.toList());
                projectIds.stream().forEach(e -> {
                    queryClassParam.getProjects().put(e, new ArrayList<>());
                });
            }
        }
        return queryClassParam;
    }
}
